<?php

/**
 * core.search class extends CatalogModule
 *
 * @author Stuart Metcalfe <shoopdev@googlemail.com>
 * @version 1.0
 *
 * Copyright (c) 2006 Shopalong
 *
 * Released under the GNU General Public License
 */

class SearchCatalogModule extends CatalogModule
{
	var $namespace = 'core';
	var $extension = 'search';
	
	var $_formObserver;
	
	function SearchCatalogModule()
	{
		global $lng, $messageStack, $currencies, $HTTP_GET_VARS;
		
		$this->_formObserver = new FormObserver('do_search');
		
		if ($this->_formObserver->isValid())
		{
			$strFormat = new AlphanumValidator($this->_formObserver);
			$dateFormat = new DateValidator($this->_formObserver);
			$numberFormat = new NumberValidator($this->_formObserver);
			
			if (isset($HTTP_GET_VARS['keywords']) && !empty($HTTP_GET_VARS['keywords']) && $strFormat->validate('keywords', 'ERROR_INVALID_KEYWORDS', 1, 60))
			{
				$keywords = $this->_parseKeywordString($HTTP_GET_VARS['keywords']);
				
				if (sizeof($keywords) < 1)
				{
					$messageStack->add('search', 'ERROR_INVALID_KEYWORDS');
				}
			}
			
			if (isset($HTTP_GET_VARS['dfrom']) && !empty($HTTP_GET_VARS['dfrom']) && $HTTP_GET_VARS['dfrom'] != DOB_FORMAT_STRING && $dateFormat->validate('dfrom', 'ERROR_INVALID_FROM_DATE'))
			{
				$dfrom = $HTTP_GET_VARS['dfrom'];
			}
			
			if (isset($HTTP_GET_VARS['dto']) && !empty($HTTP_GET_VARS['dto']) && $HTTP_GET_VARS['dto'] != DOB_FORMAT_STRING && $dateFormat->validate('dto', 'ERROR_INVALID_TO_DATE'))
			{
				$dto = $HTTP_GET_VARS['dto'];
			}
			
			if (isset($HTTP_GET_VARS['pfrom']) && !empty($HTTP_GET_VARS['pfrom']) && $numberFormat->validate('pfrom', 'ERROR_PRICE_FROM_MUST_BE_NUM'))
			{
				$pfrom = $HTTP_GET_VARS['pfrom'];
			}
			
			if (isset($HTTP_GET_VARS['pto']) && !empty($HTTP_GET_VARS['pto']) && $numberFormat->validate('pto', 'ERROR_PRICE_TO_MUST_BE_NUM'))
			{
				$pto = $HTTP_GET_VARS['pto'];
			}
			
			// Check for valid from < to date
			if (tep_not_null($dfrom) && tep_not_null($dto))
			{
				if (mktime(0, 0, 0, $dfrom_array[1], $dfrom_array[2], $dfrom_array[0]) > mktime(0, 0, 0, $dto_array[1], $dto_array[2], $dto_array[0]))
				{
					$messageStack->add('search', 'ERROR_TO_DATE_LESS_THAN_FROM_DATE');
				}
			}
			
			// Check for valid from < to price
			if (($price_check_error == false) && is_float($pfrom) && is_float($pto))
			{
				if ($pfrom >= $pto)
				{
					$messageStack->add('search', 'ERROR_PRICE_TO_LESS_THAN_PRICE_FROM');
				}
			}
			
			if (empty($dfrom) && empty($dto) && empty($pfrom) && empty($pto) && empty($keywords))
			{
				$messageStack->add('search', 'ERROR_AT_LEAST_ONE_INPUT');
			}
			
			foreach ($this->_formObserver->getErrors() as $error)
			{
				$messageStack->add('search', $error);
			}
			
			if ($messageStack->getSize('search') < 1)
			{
				$select_str = 'select distinct p.products_model, m.manufacturers_name, p.products_quantity, p.products_image, p.products_weight, m.manufacturers_id, p.products_id, pd.products_name, p.products_price, p.products_tax_class_id, IF(s.status, s.specials_new_products_price, NULL) as specials_new_products_price, IF(s.status, s.specials_new_products_price, p.products_price) as final_price ';
				
				if ( (DISPLAY_PRICE_WITH_TAX == 'true') && (tep_not_null($pfrom) || tep_not_null($pto)) )
				{
					$select_str .= ", SUM(tr.tax_rate) as tax_rate ";
				}
				
				$from_str = 'from '.TABLE_PRODUCTS.' p left join '.TABLE_MANUFACTURERS.' m using(manufacturers_id) left join '.TABLE_SPECIALS.' s on p.products_id = s.products_id';
				
				if ( (DISPLAY_PRICE_WITH_TAX == 'true') && (tep_not_null($pfrom) || tep_not_null($pto)) )
				{
					if (!tep_session_is_registered('customer_country_id'))
					{
						$customer_country_id = STORE_COUNTRY;
						$customer_zone_id = STORE_ZONE;
					}
					
					$from_str .= ' left join '.TABLE_TAX_RATES.' tr on p.products_tax_class_id = tr.tax_class_id left join '.TABLE_ZONES_TO_GEO_ZONES.' gz on tr.tax_zone_id = gz.geo_zone_id and (gz.zone_country_id is null or gz.zone_country_id = "0" or gz.zone_country_id = "'.(int) $customer_country_id.'") and (gz.zone_id is null or gz.zone_id = "0" or gz.zone_id = "'.(int) $customer_zone_id.'")';
				}
				
				$from_str .= ', '.TABLE_PRODUCTS_DESCRIPTION.' pd, '.TABLE_CATEGORIES.' c, '.TABLE_PRODUCTS_TO_CATEGORIES.' p2c';
				
				$where_str = ' where p.products_status = "1" and p.products_id = pd.products_id and pd.language_id = "'.(int)$lng->getLanguageId().'" and p.products_id = p2c.products_id and p2c.categories_id = c.categories_id ';
				
				if (isset($HTTP_GET_VARS['categories_id']) && tep_not_null($HTTP_GET_VARS['categories_id']))
				{
					if (isset($HTTP_GET_VARS['inc_subcat']) && ($HTTP_GET_VARS['inc_subcat'] == '1'))
					{
						$subcategories_array = array();
						tep_get_subcategories($subcategories_array, $HTTP_GET_VARS['categories_id']);
						
						$where_str .= ' and p2c.products_id = p.products_id and p2c.products_id = pd.products_id and (p2c.categories_id = "'.(int) $HTTP_GET_VARS['categories_id'].'"';
						
						for ($i=0, $n=sizeof($subcategories_array); $i<$n; $i++ )
						{
							$where_str .= ' or p2c.categories_id = "'.(int) $subcategories_array[$i].'"';
						}
						
						$where_str .= ')';
					}
					
					else
					{
						$where_str .= ' and p2c.products_id = p.products_id and p2c.products_id = pd.products_id and pd.language_id = "'.(int)$lng->getLanguageId().'" and p2c.categories_id = "'.(int) $HTTP_GET_VARS['categories_id'].'"';
					}
				}
				
				if (isset($HTTP_GET_VARS['manufacturers_id']) && tep_not_null($HTTP_GET_VARS['manufacturers_id']))
				{
					$where_str .= ' and m.manufacturers_id = "'.(int) $HTTP_GET_VARS['manufacturers_id'].'"';
				}
				
				if (isset($keywords))
				{
					$where_str .= ' and (';
					
					for ($i=0, $n=sizeof($keywords); $i<$n; $i++ )
					{
						switch ($keywords[$i])
						{
						case '(':
						case ')':
						case 'and':
						case 'or':
							$where_str .= ' ' . $keywords[$i] . ' ';
							break;
							
						default:
							$keyword = tep_db_prepare_input($keywords[$i]);
							$where_str .= '(pd.products_name like "%' . tep_db_input($keyword) . '%" or p.products_model like "%' . tep_db_input($keyword) . '%" or m.manufacturers_name like "%' . tep_db_input($keyword) . '%"';
							
							if (isset($HTTP_GET_VARS['search_in_description']) && ($HTTP_GET_VARS['search_in_description'] == '1'))
							{
								$where_str .= ' or pd.products_description like "%' . tep_db_input($keyword) . '%"';
							}
							
							$where_str .= ')';
							break;
						}
					}
					
					$where_str .= ' )';
				}
				
				if (tep_not_null($dfrom))
				{
					$where_str .= ' and p.products_date_added >= "' . tep_date_raw($dfrom) . '"';
				}
				
				if (tep_not_null($dto))
				{
					$where_str .= ' and p.products_date_added <= "' . tep_date_raw($dto) . '"';
				}
				
				$rate = $currencies->getCurrentRate();
				
				if (tep_not_null($pfrom))
				{
					$pfrom = $pfrom / $rate;
				}
				
				if (tep_not_null($pto))
				{
					$pto = $pto / $rate;
				}
				
				if (DISPLAY_PRICE_WITH_TAX == 'true')
				{
					if ($pfrom > 0)
					{
						$where_str .= ' and (IF(s.status, s.specials_new_products_price, p.products_price) * if(gz.geo_zone_id is null, 1, 1 + (tr.tax_rate / 100) ) >= ' . (double)$pfrom . ')';
					}
					
					if ($pto > 0)
					{
						$where_str .= ' and (IF(s.status, s.specials_new_products_price, p.products_price) * if(gz.geo_zone_id is null, 1, 1 + (tr.tax_rate / 100) ) <= ' . (double)$pto . ')';
					}
				}
				
				else
				{
					if ($pfrom > 0)
					{
						$where_str .= ' and (IF(s.status, s.specials_new_products_price, p.products_price) >= ' . (double)$pfrom . ')';
					}
					
					if ($pto > 0)
					{
						$where_str .= ' and (IF(s.status, s.specials_new_products_price, p.products_price) <= ' . (double)$pto . ')';
					}
				}
				
				if ( (DISPLAY_PRICE_WITH_TAX == 'true') && (tep_not_null($pfrom) || tep_not_null($pto)) )
				{
					$where_str .= ' group by p.products_id, tr.tax_priority';
				}
				
				if ( (!isset($HTTP_GET_VARS['sort'])) || (!ereg('[1-8][ad]', $HTTP_GET_VARS['sort'])) || (substr($HTTP_GET_VARS['sort'], 0, 1) > sizeof($column_list)) )
				{
					for ($i=0, $n=sizeof($column_list); $i<$n; $i++)
					{
						if ($column_list[$i] == 'PRODUCT_LIST_NAME')
						{
							$HTTP_GET_VARS['sort'] = $i+1 . 'a';
							$order_str = ' order by pd.products_name';
							break;
						}
					}
				}
				
				$sort_col = substr($HTTP_GET_VARS['sort'], 0 , 1);
				$sort_order = substr($HTTP_GET_VARS['sort'], 1);
				$order_str = ' order by ';
				
				switch ($column_list[$sort_col-1])
				{
				case 'PRODUCT_LIST_MODEL':
					$order_str .= 'p.products_model ' . ($sort_order == 'd' ? 'desc' : '') . ', pd.products_name';
					break;
					
				case 'PRODUCT_LIST_NAME':
					$order_str .= 'pd.products_name ' . ($sort_order == 'd' ? 'desc' : '');
					break;
					
				case 'PRODUCT_LIST_MANUFACTURER':
					$order_str .= 'm.manufacturers_name ' . ($sort_order == 'd' ? 'desc' : '') . ', pd.products_name';
					break;
					
				case 'PRODUCT_LIST_QUANTITY':
					$order_str .= 'p.products_quantity ' . ($sort_order == 'd' ? 'desc' : '') . ', pd.products_name';
					break;
					
				case 'PRODUCT_LIST_IMAGE':
					$order_str .= 'pd.products_name';
					break;
					
				case 'PRODUCT_LIST_WEIGHT':
					$order_str .= 'p.products_weight ' . ($sort_order == 'd' ? 'desc' : '') . ', pd.products_name';
					break;
					
				case 'PRODUCT_LIST_PRICE':
				default:
					$order_str .= 'final_price ' . ($sort_order == 'd' ? 'desc' : '') . ', pd.products_name';
					break;
				}
				
				$this->_sqlQuery = $select_str.$from_str.$where_str.$order_str;
			}
		}
	}
	
	function getTemplate()
	{
		global $messageStack, $HTTP_GET_VARS;
		
		if ($this->_formObserver->isValid() && $messageStack->getSize('search') < 1)
		{
			return CatalogModule::getTemplate();
		}
		
		else
		{
			return DIR_FS_MODULES.$this->namespace.'.'.$this->extension.'/search_form.tpl';
		}
	}
	
	function getConfig()
	{
		global $lng;
		
		$langFile = DIR_FS_MODULES.$this->namespace.'.'.$this->extension.'/'.$lng->getLanguage().'.conf';
		
		if (!file_exists($langFile))
		{
			$langFile = CatalogModule::getConfig();
		}
		
		return $langFile;
	}
	
	function _parseKeywordString($searchStr)
	{
		// Parse search string into indivual objects
		$searchStr = trim(strtolower($searchStr));
		$pieces = split('[[:space:]]+', $searchStr);
		$objects = array();
		$tmpstring = '';
		$flag = '';
		
		for ($k=0; $k<count($pieces); $k++)
		{
			while (substr($pieces[$k], 0, 1) == '(')
			{
				$objects[] = '(';
				
				if (strlen($pieces[$k]) > 1)
				{
					$pieces[$k] = substr($pieces[$k], 1);
				}
				
				else
				{
					$pieces[$k] = '';
				}
			}
			
			$post_objects = array();
			
			while (substr($pieces[$k], -1) == ')')
			{
				$post_objects[] = ')';
				
				if (strlen($pieces[$k]) > 1)
				{
					$pieces[$k] = substr($pieces[$k], 0, -1);
				}
				
				else
				{
					$pieces[$k] = '';
				}
			}
			
			// Check individual words
			
			if ((substr($pieces[$k], -1) != '"') && (substr($pieces[$k], 0, 1) != '"'))
			{
				$objects[] = trim($pieces[$k]);
				
				for ($j=0; $j<count($post_objects); $j++)
				{
					$objects[] = $post_objects[$j];
				}
			}
			
			else
			{
				/*
				 * This means that the $piece is either the beginning or the end
				 * of a string.  So, we'll slurp up the $pieces and stick them
				 * together until we get to the end of the string or run out of
				 * pieces.
				 */
				
				// Add this word to the $tmpstring, starting the $tmpstring
				$tmpstring = trim(ereg_replace('"', ' ', $pieces[$k]));
				
				// Check for one possible exception to the rule. That there is a single quoted word.
				if (substr($pieces[$k], -1 ) == '"')
				{
					// Turn the flag off for future iterations
					$flag = 'off';
					
					$objects[] = trim($pieces[$k]);
					
					for ($j=0; $j<count($post_objects); $j++)
					{
						$objects[] = $post_objects[$j];
					}
					
					unset($tmpstring);
					
					// Stop looking for the end of the string and move onto the next word.
					continue;
				}
				
				// Otherwise, turn on the flag to indicate no quotes have been found attached to this word in the string.
				$flag = 'on';
				
				// Move on to the next word
				$k++;
				
				// Keep reading until the end of the string as long as the $flag is on
				
				while (($flag == 'on') && ($k < count($pieces)))
				{
					while (substr($pieces[$k], -1) == ')')
					{
						$post_objects[] = ')';
						
						if (strlen($pieces[$k]) > 1)
						{
							$pieces[$k] = substr($pieces[$k], 0, -1);
						}
						
						else
						{
							$pieces[$k] = '';
						}
					}
					
					// If the word doesn't end in double quotes, append it to the $tmpstring.
					if (substr($pieces[$k], -1) != '"')
					{
						// Tack this word onto the current string entity
						$tmpstring .= ' ' . $pieces[$k];
						
						// Move on to the next word
						$k++;
						continue;
					}
					
					else
					{
						/*
						 * If the $piece ends in double quotes, strip the double
						 * quotes, tack the $piece onto the tail of the string,
						 * push the $tmpstring onto the $haves, kill the
						 * $tmpstring, turn the $flag "off", and return.
						 */
						$tmpstring .= ' ' . trim(ereg_replace('"', ' ', $pieces[$k]));
						
						// Push the $tmpstring onto the array of stuff to search for
						$objects[] = trim($tmpstring);
						
						for ($j=0; $j<count($post_objects); $j++)
						{
							$objects[] = $post_objects[$j];
						}
						
						unset($tmpstring);
						
						// Turn off the flag to exit the loop
						$flag = 'off';
					}
				}
			}
		}
		
		// add default logical operators if needed
		$temp = array();
		
		for($i=0; $i<(count($objects)-1); $i++)
		{
			$temp[] = $objects[$i];
			if (($objects[$i] != 'and') && ($objects[$i] != 'or') && ($objects[$i] != '(') && ($objects[$i+1] != 'and') && ($objects[$i+1] != 'or') && ($objects[$i+1] != ')'))
			{
				$temp[] = ADVANCED_SEARCH_DEFAULT_OPERATOR;
			}
		}
		
		$temp[] = $objects[$i];
		$objects = $temp;
		$keyword_count = 0;
		$operator_count = 0;
		$balance = 0;
		
		for($i=0; $i<count($objects); $i++)
		{
			if ($objects[$i] == '(')
			{
				$balance --;
			}
			
			if ($objects[$i] == ')')
			{
				$balance ++;
			}
			
			if (($objects[$i] == 'and') || ($objects[$i] == 'or'))
			{
				$operator_count ++;
			}
			
			elseif
			(($objects[$i]) && ($objects[$i] != '(') && ($objects[$i] != ')'))
			{
				$keyword_count ++;
			}
		}
		
		if (($operator_count < $keyword_count) && ($balance == 0))
		{
			return $objects;
		}
		
		else
		{
			return array();
		}
	}
}

?>
